    <?php
    /** 
     * CodeIgniter Redis 
     * 
     * A CodeIgniter library to interact with Redis 
     * 
     * @package         CodeIgniter 
     * @category        Libraries 
     * @author          Joël Cox 
     * @version         v0.4 
     * @link            https://github.com/joelcox/codeigniter-redis 
     * @link            https://joelcox.nl
     * @license         https://www.opensource.org/licenses/mit-license.html
     */  
    class CI_Redis {  
      
        /** 
         * CI 
         * 
         * CodeIgniter instance 
         * @var     object 
         */  
        private $_ci;  
      
        /** 
         * Connection 
         * 
         * Socket handle to the Redis server 
         * @var     handle 
         */  
        private $_connection;  
      
        /** 
         * Debug 
         * 
         * Whether we're in debug mode 
         * @var     bool 
         */  
        public $debug = FALSE;  
      
        /** 
         * CRLF 
         * 
         * User to delimiter arguments in the Redis unified request protocol 
         * @var     string 
         */  
        const CRLF = "\r\n";  
      
        /** 
         * Constructor 
         */  
        public function __construct($params = array())  
        {   
            // Is the config file in the environment folder?
            if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/redis.php'))
            {
                if ( ! file_exists($file_path = APPPATH.'config/redis.php'))
                {
                    $this->show_error('The configuration file redis.php'.' does not exist.');
                }
            }
            
            include($file_path);

            if ( ! isset($redis_config) OR count($redis_config) == 0)
            {
                $this->show_error('No redis connection settings were found in the redis config file.');
            }
            //$redis_config = $this->redis_config;
            // Check for the different styles of configs  
            if (isset($params['connection_group']))  
            {  
                // Specific connection group  
                $config = $redis_config['redis_' . $params['connection_group']];  
            }  
            elseif (is_array($redis_config['redis_default']))  
            {  
                // Default connection group  
                $config = $redis_config['redis_default'];  
            }  
            else  
            {  
                // Original config style  
                $config = array(  
                    'host' => $redis_config['redis_host'],  
                    'port' => $redis_config['redis_port'],  
                    'password' => $redis_config['redis_password'],  
                );  
            }  
      
            // Connect to Redis  
            $this->_connection = @fsockopen($config['host'], $config['port'], $errno, $errstr, 3);  
      
            // Display an error message if connection failed  
            if ( ! $this->_connection)  
            {  
                $this->show_error('Could not connect to Redis at ' . $config['host'] . ':' . $config['port']);  
            }  
      
            // Authenticate when needed  
            $this->_auth($config['password']);  
      
        }  
      
        /** 
         * Call 
         * 
         * Catches all undefined methods 
         * @param   string  method that was called 
         * @param   mixed   arguments that were passed 
         * @return  mixed 
         */  
        public function __call($method, $arguments)  
        {  
            $request = $this->_encode_request($method, $arguments);  
            return $this->_write_request($request);  
        }  
      
        /** 
         * Command 
         * 
         * Generic command function, just like redis-cli 
         * @param   string  full command as a string 
         * @return  mixed 
         */  
        public function command($string)  
        {  
            $slices = explode(' ', $string);  
            $request = $this->_encode_request($slices[0], array_slice($slices, 1));  
      
            return $this->_write_request($request);  
        }  
      
        /** 
         * Auth 
         * 
         * Runs the AUTH command when password is set 
         * @param   string  password for the Redis server 
         * @return  void 
         */  
        private function _auth($password = NULL)  
        {  
      
            // Authenticate when password is set  
            if ( ! empty($password))  
            {  
      
                // See if we authenticated successfully  
                if ($this->command('AUTH ' . $password) !== 'OK')  
                {  
                    $this->show_error('Could not connect to Redis, invalid password');  
                }  
      
            }  
      
        }  
      
        /** 
         * Clear Socket 
         * 
         * Empty the socket buffer of theconnection so data does not bleed over 
         * to the next message. 
         * @return  NULL 
         */  
        public function _clear_socket()  
        {  
            // Read one character at a time  
            fflush($this->_connection);  
            return NULL;  
        }  
      
        /** 
         * Write request 
         * 
         * Write the formatted request to the socket 
         * @param   string  request to be written 
         * @return  mixed 
         */  
        private function _write_request($request)  
        {  
            if ($this->debug === TRUE)  
            {  
            }  
      
            // How long is the data we are sending?  
            $value_length = strlen($request);  
      
            // If there isn't any data, just return  
            if ($value_length <= 0) return NULL;  
      
      
            // Handle reply if data is less than or equal to 8192 bytes, just send it over  
            if ($value_length <= 8192)  
            {  
                fwrite($this->_connection, $request);  
            }  
            else  
            {  
                while ($value_length > 0)  
                {  
      
                    // If we have more than 8192, only take what we can handle  
                    if ($value_length > 8192) {  
                        $send_size = 8192;  
                    }  
      
                    // Send our chunk  
                    fwrite($this->_connection, $request, $send_size);  
      
                    // How much is left to send?  
                    $value_length = $value_length - $send_size;  
      
                    // Remove data sent from outgoing data  
                    $request = substr($request, $send_size, $value_length);  
      
                }  
            }  
      
            // Read our request into a variable  
            $return = $this->_read_request();  
      
            // Clear the socket so no data remains in the buffer  
            $this->_clear_socket();  
      
            return $return;  
        }  
      
        /** 
         * Read request 
         * 
         * Route each response to the appropriate interpreter 
         * @return  mixed 
         */  
        private function _read_request()  
        {  
            $type = fgetc($this->_connection);  
      
            // Times we will attempt to trash bad data in search of a  
            // valid type indicator  
            $response_types = array('+', '-', ':', '$', '*');  
            $type_error_limit = 50;  
            $try = 0;  
      
            while ( ! in_array($type, $response_types) && $try < $type_error_limit)  
            {  
                $type = fgetc($this->_connection);  
                $try++;  
            }  
      
            if ($this->debug === TRUE)  
            {  
            }  
      
            switch ($type)  
            {  
                case '+':  
                    return $this->_single_line_reply();  
                    break;  
                case '-':  
                    return $this->_error_reply();  
                    break;  
                case ':':  
                    return $this->_integer_reply();  
                    break;  
                case '$':  
                    return $this->_bulk_reply();  
                    break;  
                case '*':  
                    return $this->_multi_bulk_reply();  
                    break;  
                default:  
                    return FALSE;  
            }  
      
        }  
      
        /** 
         * Single line reply 
         * 
         * Reads the reply before the EOF 
         * @return  mixed 
         */  
        private function _single_line_reply()  
        {  
            $value = rtrim(fgets($this->_connection));  
            $this->_clear_socket();  
      
            return $value;  
        }  
      
        /** 
         * Error reply 
         * 
         * Write error to log and return false 
         * @return  bool 
         */  
        private function _error_reply()  
        {  
            // Extract the error message  
            $error = substr(rtrim(fgets($this->_connection)), 4);  
      
            $this->_clear_socket();  
      
            return FALSE;  
        }  
      
        /** 
         * Integer reply 
         * 
         * Returns an integer reply 
         * @return  int 
         */  
        private function _integer_reply()  
        {  
            return (int) rtrim(fgets($this->_connection));  
        }  
      
        /** 
         * Bulk reply 
         * 
         * Reads to amount of bits to be read and returns value within 
         * the pointer and the ending delimiter 
         * @return  string 
         */  
        private function _bulk_reply()  
        {  
      
            // How long is the data we are reading? Support waiting for data to  
            // fully return from redis and enter into socket.  
            $value_length = (int) fgets($this->_connection);  
      
            if ($value_length <= 0) return NULL;  
      
            $response = '';  
      
            // Handle reply if data is less than or equal to 8192 bytes, just read it  
            if ($value_length <= 8192)  
            {  
                $response = fread($this->_connection, $value_length);  
            }  
            else  
            {  
                $data_left = $value_length;  
      
                    // If the data left is greater than 0, keep reading  
                    while ($data_left > 0 ) {  
      
                    // If we have more than 8192, only take what we can handle  
                    if ($data_left > 8192)  
                    {  
                        $read_size = 8192;  
                    }  
                    else  
                    {  
                        $read_size = $data_left;  
                    }  
      
                    // Read our chunk  
                    $chunk = fread($this->_connection, $read_size);  
      
                    // Support reading very long responses that don't come through  
                    // in one fread  
      
                    $chunk_length = strlen($chunk);  
                    while ($chunk_length < $read_size)  
                    {  
                        $keep_reading = $read_size - $chunk_length;  
                        $chunk .= fread($this->_connection, $keep_reading);  
                        $chunk_length = strlen($chunk);  
                    }  
      
                    $response .= $chunk;  
      
                    // Re-calculate how much data is left to read  
                    $data_left = $data_left - $read_size;  
      
                }  
      
            }  
      
            // Clear the socket in case anything remains in there  
            $this->_clear_socket();  
      
        return isset($response) ? $response : FALSE;  
        }  
      
        /** 
         * Multi bulk reply 
         * 
         * Reads n bulk replies and return them as an array 
         * @return  array 
         */  
        private function _multi_bulk_reply()  
        {  
            // Get the amount of values in the response  
            $response = array();  
            $total_values = (int) fgets($this->_connection);  
      
            // Loop all values and add them to the response array  
            for ($i = 0; $i < $total_values; $i++)  
            {  
                // Remove the new line and carriage return before reading  
                // another bulk reply  
                fgets($this->_connection, 2);  
      
                // If this is a second or later pass, we also need to get rid  
                // of the $ indicating a new bulk reply and its length.  
                if ($i > 0)  
                {  
                    fgets($this->_connection);  
                    fgets($this->_connection, 2);  
                }  
      
                $response[] = $this->_bulk_reply();  
      
            }  
      
            // Clear the socket  
            $this->_clear_socket();  
      
            return isset($response) ? $response : FALSE;  
        }  
      
        /** 
         * Encode request 
         * 
         * Encode plain-text request to Redis protocol format 
         * @link    https://redis.io/topics/protocol
         * @param   string  request in plain-text 
         * @param   string  additional data (string or array, depending on the request) 
         * @return  string  encoded according to Redis protocol 
         */  
        private function _encode_request($method, $arguments = array())  
        {  
            $request = '$' . strlen($method) . self::CRLF . $method . self::CRLF;  
            $_args = 1;  
      
            // Append all the arguments in the request string  
            foreach ($arguments as $argument)  
            {  
                if (is_array($argument))  
                {  
                    foreach ($argument as $key => $value)  
                    {  
                        // Prepend the key if we're dealing with a hash  
                        if (!is_int($key))  
                        {  
                            $request .= '$' . strlen($key) . self::CRLF . $key . self::CRLF;  
                            $_args++;  
                        }  
      
                        $request .= '$' . strlen($value) . self::CRLF . $value . self::CRLF;  
                        $_args++;  
                    }  
                }  
                else  
                {  
                    $request .= '$' . strlen($argument) . self::CRLF . $argument . self::CRLF;  
                    $_args++;  
                }  
            }  
      
            $request = '*' . $_args . self::CRLF . $request;  
      
            return $request;  
        }  
      
        /** 
         * Info 
         * 
         * Overrides the default Redis response, so we can return a nice array 
         * of the server info instead of a nasty string. 
         * @return  array 
         */  
        public function info($section = FALSE)  
        {  
            if ($section !== FALSE)  
            {  
                $response = $this->command('INFO '. $section);  
            }  
            else  
            {  
                $response = $this->command('INFO');  
            }  
      
            $data = array();  
            $lines = explode(self::CRLF, $response);  
      
            // Extract the key and value  
            foreach ($lines as $line)  
            {  
                $parts = explode(':', $line);  
                if (isset($parts[1])) $data[$parts[0]] = $parts[1];  
            }  
      
            return $data;  
        }  
      
        /** 
         * Debug 
         * 
         * Set debug mode 
         * @param   bool    set the debug mode on or off 
         * @return  void 
         */  
        public function debug($bool)  
        {  
            $this->debug = (bool) $bool;  
        }  
      
        /** 
         * Destructor 
         * 
         * Kill the connection 
         * @return  void 
         */  
        function __destruct()  
        {  
            if ($this->_connection) fclose($this->_connection);  
        }  

        private function show_error($message)
        {
            if(is_array($message)){
                var_dump($message);
            }else{
                echo $message;
            }
        }
    }  
      
    ?>  